<?php
/*
        @desc for easy authentication handling
        @author xq262144
*/

class Auth {
    /*

    */
    const SALT_LEN = 6;

    /*
            @access private
            @var Object $_db
            @var bool $_inited
            @var int $_rememberTime
            @var string $_tblName
            @var string $_loginPage
    */
    private static $_db = null;
    private static $_inited = false;
    private static $_rememberTime = 2592000;
    private static $_tblName = 'user';
    private static $_loginPage = '';

    /*
            @access private
            @desc handling authentication class initialization
    */
    private static function _init( ) {
        global $auth;

        if ( self::$_inited ) {
            return false;
        } else {
            self::$_db = getDb();

            if ( isset( $auth['table'] ) ) {
                self::$_tblName = $auth['table'];
            }
            if ( isset( $auth['page'] ) ) {
                self::$_loginPage = $auth['page'];
            }
            if ( isset( $auth['time'] ) ) {
                self::$_rememberTime = $auth['time'];
            }

            return true;
        }
    }

    /*
            @access private
            @param string $username
            @return bool authentication successful state
            @desc handling authentication class main session authorization
    */
    private static function _authorize( $username ) {
        $sql = 'select count(*) as valid from ' . self::$_tblName
                . ' where username = {0} limit 1';
        $info = self::$_db->fetchOne( $sql, array( $username ) );
        if ( 0 !== strcasecmp( $info['valid'], '0' ) ) {
            //exist
            if ( !isset( $_SESSION ) ) session_start( );

            $_SESSION['auth'] = array();
            $_SESSION['auth']['username'] = $username;

            return true;
        } else return false;
    }

    /*
            @access private
            @return bool always true with no error or exception occured
            @desc handling authentication clas main session unauthorization
    */
    private static function _unauthorize( ) {
        if ( !isset( $_SESSION ) ) session_start( );
        unset( $_SESSION['auth'] );
        return true;
    }

    /*
            @access private
            @return bool cookie login attempt successful state
            @desc handling user login using cookie stored information with cookie validation checking
    */
    private static function _cookieLogin( ) {
        if ( self::_checkCookieIdCode( ) ) {
            if ( self::_authorize( $_COOKIE['username'] ) ) {
                return true;
            } else return false;
        } else return false;
    }

    /*
            @access private
            @return bool cookie forgotion successful state
            @desc handling cookie user authorization info forgotion
    */
    private static function _forgetInCookie( ) {
        setcookie( 'username', '', null, '/' );
        setcookie( 'idCode', '', null, '/' );
        unset( $_COOKIE['username'] );
        unset( $_COOKIE['idCode'] );
        return true;
    }

    /*
            @access private
            @param string $username username of user to remember cookie encoding
            @return bool always true for no error in cookie authorization info setting
            @desc handling cookie user authorization info be written
    */
    private static function _rememberInCookie( $username ) {
        setcookie( 'username', $username, time( ) + self::$_rememberTime, '/' );
        setcookie( 'idCode',
                self::_generateCookieIdCode( $username ),
                time( ) + self::$_rememberTime,'/' );
        return true;
    }

    /*
            @access private
            @param string $passwd password of user to be encrypted
            @return string encrypted password
            @desc handling user password encryption
    */
    private static function _encryptPasswd( $passwd ) {
        return md5( $passwd );
    }

    /*
            @access private
            @param string $username username to be doing password checking
            @param string $password password to be checked
            @return bool state of password correction
            @desc handling user password checking
    */
    private static function _checkPassword( $username, $password ) {
        self::_init( );
        $sql = 'select count(*) as valid from ' . self::$_tblName
                . ' where username = {0} and password = {1} limit 1';
        $info = self::$_db->fetchOne( $sql,
                array( $username, self::_encryptPasswd( $password ) ) );
        return (0 !== strcasecmp( $info['valid'], '0' ) );
    }

    /*
            @accces private
            @param string $username username to generate unique cookie login string
            @param string $hash salt hash of the cookie identification string
            @return string generated cookie login string
            @desc handling user cookie code generation
    */
    private static function _generateCookieIdCode( $username, $hash = null ) {
        $hash = is_null( $hash ) ? substr( md5( rand( ) ), 0,
                self::SALT_LEN ) : $hash;
        return $hash . '$$' . sha1( $hash . '$$' . $username );
    }

    /*
            @access private
            @return bool checking state of cookie stored user authorization information
            @desc handling cookie login code checking
    */
    private static function _checkCookieIdCode( ) {
        if ( !isset( $_COOKIE['idCode'] ) || !isset( $_COOKIE['username'] ) ) {
            return false;
        }

        $code = $_COOKIE['idCode'];
        $username = $_COOKIE['username'];

        if ( false !== ( $pos = strpos( $code, '$$' ) ) ) {
            //correct cookieIdCode
            $hash = substr( $code, 0, $pos );
            return ( 0 === strcasecmp(
                            self::_generateCookieIdCode( $username, $hash ), $code ) );
        } else return false;
    }

    /*
            @access public
            @param string $username username to modify password
            @param string $from old password
            @param string $to new password
            @desc handling user password modification
    */
    public static function modifyPassword( $username, $from, $to ) {
        self::_init( );
        if ( self::_checkPassword( $username, $from ) ) {
            //modify
            $sql =  'update ' . self::$_tblName .
                    ' set password = {0} where username = {1} limit 1';
            return self::$_db->query( $sql,
                    array( self::_encryptPasswd( $to ), $username ) );
        } else return false;
    }

    /*
            @access public
            @param string $username username to login
            @param string $password password to login
            @param bool true to remeber user login state in cookie
            @return bool login successful state
            @desc handling user login attemption
    */
    public static function login( $username, $password, $remember = false ) {
        self::_init( );
        if ( !self::_checkPassword( $username, $password ) ) {
            return false;
        } else {
            self::_authorize( $username );

            if ( $remember ) {
                self::_rememberInCookie( $username );
            }

            return true;
        }
    }

    /*
            @access public
            @return bool logout state of user
            @desc handling user logout attemption
    */
    public static function logout( ) {
        self::_init( );
        //logged in
        self::_forgetInCookie( );
        self::_unauthorize( );
        return true;
    }

    /*
            @access public
            @return bool login state
            @desc handling login state checking
    */
    public static function checkLogin( ) {
        self::_init( );
        if ( !isset( $_SESSION ) ) session_start( );

        if ( isset( $_SESSION['auth']['username'] ) ) {
            return true;
        } else {
            //then try cookie
            return self::_cookieLogin( );
        }
    }

    /*
            @access public
            @return bool login state
            @desc handling page login requirement and redirect
    */
    public static function requireLogin( ) {
        self::_init( );
        if ( !self::checkLogin( ) ) {
            $head = 'Location: ' . URL_BASE . self::$_loginPage . '?redirectURL=' .
                    urlencode( 'http://' .
                            $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] ) ;
            header( $head );
            die;
        } else return true;
    }

    /*
            @access public
            @return string username of people logged in
            @desc handling logged in user information fetching
    */
    public static function getUserName( ) {
        if ( self::checkLogin( ) ) {
            return $_SESSION['auth']['username'];
        } else {
            return null;
        }
    }

    /*
            @access public
            @param string $username
            @param string $password
            @return int | bool regist successful state, successful return userid
            @desc handling user regist
    */
    public static function regist( $username, $password ) {
        if ( !self::checkAvail( $username ) ) {
            return false;
        }

        $sql = 'insert into ' . self::$_tblName . ' (username, password) values({0},{1})';
        if ( self::$_db->query( $sql, array( $username,
        self::_encryptPasswd( $password ) ) ) ) {
            return self::$_db->insertId( );
        } else {
            return false;
        }
    }

    /*
            @access public
            @param string $username
            @return bool user availability state
            @desc checking user existence state, true for not exists
    */
    public static function checkAvail( $username ) {
        $sql = 'select count(*) as valid from ' . self::$_tblName
                . ' where username = {0} limit 1';
        $state = self::$_db->fetchOne( $sql, array( $username ) );
        return 0 === strcasecmp( $state['valid'], '0' );
    }
}